/*
 * MPEGFrame.h
 *     -- define frame format for MPEG file
 * frame data layouts are as follow(prefixing a frame header to compose a complete frame):
 *     _________________________________________________________________________________________________________________
 *     |side info|scalefactors|huffman data|scalefactor|huffman data|scalefactors|huffman data|scalefactor|huffman data|
 *     -----------------------------------------------------------------------------------------------------------------
 *               |       channel 1         |        channel 2       |         channel 1       |        channel 2       |
 *               -------------------------------------------------------------------------------------------------------
 *               |                     granule 1                    |                    granule 2                     |
 *     -----------------------------------------------------------------------------------------------------------------
 *     |                                                  encoded data                                                 |
 *     -----------------------------------------------------------------------------------------------------------------
 *
 * Author: Qiu Shuang
 * On: Mar 1st, 2011
 */
#ifndef MPEGFRAME_H
#define MPEGFRAME_H

#include "metadata.h"
#include "MPEGDecodable.h"
#include "MPEGHuffmanTable.h"

/* forward declaration */
class MPEGFrameHeader;
class MPEGSideInfo;
class MPEGGranule;

/* class defination */
class MPEGFrameData{
    friend class MPEGGranule;
public:
    /* constructor
     *     take an open mp3 file as argument, we simply assume cursor of
     *     the file is pointing to the right position.
     */
    MPEGFrameData(FILE *mpeg, MPEGFrameHeader *frame_header);

    /* destructor */
    ~MPEGFrameData();

    /* frame specified MPEG decoder */
    int decode(const unsigned char *buf);

    /* dummy function */
    const unsigned char *decodedContent(int granule, int channel) const;

    /* get data on which further manipulation is performed */
    const ChannelDataDouble *getData(int granule, int channel) const;

    /* field accessor */
    MPEGFrameHeader *frameHeader();
    MPEGSideInfo    *sideInfo();
    unsigned short CRC();
    MPEGGranule *getGranule(int granule);

private:
    /* buffer main data */
    bool buffer_data();

    /* read specified number of bits from data buffer */
    unsigned int readbits(int bit_num);

    /* decode scale factor for specific channel */
    void decode_scalefactor(int granule, int channel);

    void decode_scale_I(int granule, int channel);
    unsigned int slen1 (unsigned int channel, unsigned int granule);
    unsigned int slen2 (unsigned int channel, unsigned int granule);

    /* decode huffman encoding in specific channel */
    bool decode_huffman(int granlue, int channel);

    /* decode pairs of data in a given huffman encoding tree */
    bool huffman_decoder(HuffmanCodeTree *code_tree, unsigned int *x, unsigned *y,
                         unsigned int *v, unsigned int *w);

    bool _other_huffman_decoder(HuffmanCodeTree *code_tree, unsigned int *x, unsigned *y,
                         unsigned int *v, unsigned int *w);


    /* set pointer to where intermediate buffer start skipping any axilliary data from
     * the previous main_data */
    bool set_bufstart();

    /* saves remaining data of the frame to the interim buffer */
    bool save_to_interbuffer();

    FILE *_mpeg;                            /* underlying MPEG file */
    MPEGFrameHeader *_frame_header;         /* pointer to frame header */
    unsigned short  _crc;                   /* CRC verification code */
    MPEGSideInfo    *_sideinfo;             /* side information */
    MPEGGranule     *_granules[GRANULE];    /* granules in a frame */

    /* accumulating how many bits read from main buffer */
    char _data_buffer[MAX_BUFSIZE];                         /* maximum data buffer size */
    int _bytes_read;                                        /* bytes position should be read in local data buffer */
    int _bits_offset;                                       /* bit offset in current byte, utilized by both local and global */

    int _main_data_bitsread;                                /* number of bits read in interim buffer */
    int _spectral_bitsread;
    int _end_bits;

    /* scale factor length table */
    static const unsigned int SCALE_LEN[2][16];             /* table for scalefactor length */
    static const unsigned int SCALEBAND_TABLE[2][5];        /* table for scale band division */

    /* data below will be set null when end of stream reached */
    /* global buffer used to extract MP3 file */
    static char _interim_buffer[MAX_INTERMEDIATE_BUFSIZE];  /* a circular buffer taking any spare
                                                             * bytes from the frame's buffer to
                                                             * store between indexes bufstart and
                                                             * bufend */
    static int _bufstart;           /* indicating where buffer starts */
    static int _bufend;             /* indicating where buffer ends */
    static int _window_no;          /* window number */

    /* scale factor table for long window */
    unsigned int _longwin_scalefac[GRANULE][CHANNEL][LONGWIN_SCALEFACTABLE];
    /* scale factor table for short window */
    unsigned int _shortwin_scalefac[GRANULE][CHANNEL][SHORTWIN_PER_GRANULE][SHORTWIN_SCALEFACTABLE];

#ifdef DEBUG
    void printHuffman(int granule, int channel, std::ofstream &output);
    void printRestored(int granule, int channel, std::ofstream &output);
    void printStereo(int granule, std::ofstream &output);
#endif
};

/* field accessor */
inline MPEGFrameHeader *MPEGFrameData::frameHeader(){
    return _frame_header;
}

inline MPEGSideInfo *MPEGFrameData::sideInfo(){
    return _sideinfo;
}

inline unsigned short MPEGFrameData::CRC(){
    return _crc;
}

inline MPEGGranule *MPEGFrameData::getGranule(int granule){
    return _granules[granule];
}

#endif